Como utilizar template-driven forms para gestionar inputs en tiempo real
Para poder trabajar con los Template driven forms primero tenemos que importar FormsModule en el archivo app.module.ts
Para utilizar los template-driven forms principalmente se hace con el uso de la directiva [(ngModel)]
Para aplicarlo de manera sencilla sobre un input lo hacemos de la siguiente manera:
<p>Template driven form en un mismo componente</p>
<input [(ngModel)]="textoInput"/>
<p>Texto: {{textoInput}}</p>
textoInput es una variable definida en el componente:
En este ejemplo, tan pronto escribamos algun en el input se actualizará instantaneamente en la variable textoInput y por lo tanto se mostrará actualizada dentro de la etiqeta <p>
Otra manera de implementar los template-driven forms es usando el two-way binding para comunicar un componente padre y un componente hijo de manera que se actualicen instantaneamente en base a un input que haya en uno de los dos componentes.
En este caso tenemos en el template del componente padre el siguiente código:
<p>Template driven form padre hijo</p>
<app-hijo [(dataInput)]="dataInput"></app-hijo>
<p>Padre: <input [(ngModel)]="dataInput" /></p>
<p>Texto: {{dataInput}}</p>
En la etiqueta del componente hijo tenemos el two-way binding, el dataInput que está a la izquierda hace referencia al @Input() que hay en el componente hijo, y el que está a la derecha hace referencia a la variable dataInput del componente padre, que se actualizará tan pronto el hijo lanze un emitter para esa variable.
En el template del hijo tenemos lo siguiente:
<p>Hijo: <input (input)="send()" [(ngModel)]="dataInput"></p>
<select (change)="send()" [(ngModel)]="dataInput">
<option [value]="'opción 1'">opción 1</option>
<option [value]="'opción 2'">opción 2</option>
<option [value]="'opción 3'">opción 3</option>
</select>
Aqui tenemos definidos dos inputs, uno de texto y otro un select, ambos cuando detectan cambios llaman a la funcion send() que lo único que hace es emitir la información de la variable dataInput y a parte tambien tienen definida la directiva [(ngModel)] para actualizar la información de la variable al instante.
Y en el componente del hijo tenemos definido lo siguiente:
Con esta implementación tanto si se modifica el input del hijo (ya sea el de texto o el select) como el del padre, se actualizan todos al mismo tiempo
En el caso de que queramos usar validaciónes sobre los inputs podemos hacerlo con las etiquetas estandar de HTML, algunas de las que podemos usar son:
La manera de validar es la siguiente:
<p>Padre: <input minlength="3" email [(ngModel)]="dataInputValidation" #dataValidation="ngModel" /></p>
<div [hidden]="dataValidation.valid">
Input not valid
</div>
En este caso usamos las validaciones de email y minLength
Lo primero que tenemos que hacer es asociar el una template variable con el ngModel se hace con esto:
#dataValidation="ngModel"
Y a continuación podemos usar el valor que asignamos para comprobar las validaciones y mostrar el mensaje que hay en el div o no, en este caso es:
[hidden]="dataValidation.valid"
Otro ejemplo sería el siguiente:
<input required (input)="dataEmitter()" [(ngModel)]="dataInputValidation" #dataValidation="ngModel" />
<div [hidden]="dataValidation.valid || dataValidation.pristine">
Invalid data
</div>
En este ejemplo usamos a mayores del atributo .valid el atributo .pristine, este atributo lo que hace es devolver true cuando el input aún no ha sido modificado por el usuario, de manera que cuando se carga la página por defecto no se muestra el error en caso de que la validación no sea correcta, ya que el usuario todavía no ha realizado ningún input.
Usando el ngControl nos evitamos tener que utilizar el two-way binding para envíar la información del hijo al padre, y ademas nos permite realizar el uso de validaciones de manera mas eficiente.
Primero en el componente padre definimos un formulario y dentro el componente hijo donde iran los inputs del formulario:
<form style="margin-top: 2rem" (ngSubmit)="onSubmit(form)" #form="ngForm">
<app-child
name="userName"
[required]="true"
[minlength]="3"
ngModel>
</app-child>
<p>
<button type="submit" [disabled]="form.invalid">Submit</button>
</p>
</form>
Es importante tener en cuenta:
En el template del componente hijo tendremos un código como el siguiente:
<div class="input-wrapper">
<input type="text"
[required]="required"
minlength="minlength"
[disabled]="disabled"
[(ngModel)]="data"
(ngModelChange)="onChangeFunction(data)"
(blur)="onTouchedFunction()"
/>
<div class="error-message" *ngIf="showError">
{{error}}
</div>
</div>
Cosas a tener en cuenta de este código:
Ahora veremos el código del componente hijo (donde está la miga del asunto):
import { Component, Input, Optional, Self } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements ControlValueAccessor {
@Input() public label!: string;
@Input() public required = false;
@Input() public minlength = 0;
@Input() public disabled = false;
@Input() public data!: string;
constructor(@Self() @Optional() public control: NgControl) {
this.control && (this.control.valueAccessor = this);
}
public onChangeFunction!: Function;
public onTouchedFunction!: Function;
public get invalid(): boolean | null {
return this.control ? this.control.invalid : false;
}
public get showError(): boolean | null {
if (!this.control) {
return false;
}
const { dirty, touched } = this.control;
return this.invalid ? (dirty || touched) : false;
//Este código equivale al código superior sin la constante
//return this.invalid ? (this.control.dirty || this.control.touched) : false;
}
public get error() :string | void{
if(!this.control){
return "";
}
if(this.control.errors!['required']){
return "Debes rellenar este campo";
}
if(this.control.errors!['minlength']){
return "Minimo 3 caracteres";
}
}
public registerOnChange(onChangeFunction: any){
this.onChangeFunction = onChangeFunction;
}
public registerOnTouched(onTouchedFunction: any){
this.onTouchedFunction = onTouchedFunction;
}
public writeValue(newData: string){
this.data = newData;
}
}
Lo primero que tenemos que tener en cuenta es la implementación en la clase de la interfaz ControlValueAccessor esto nos obliga a declarar las siguientes funciones de manera obligatoria:
En el constructor declaramos el ngControl, tal que asi:
constructor(@Self() @Optional() public control: NgControl) {
this.control && (this.control.valueAccessor = this);
}
Despues tenemos que tener en cuenta el getter de showError que nos indicará si el input contiene un valor correcto o no
Y a mayores el getter de error, que nos devuelve una cadena de error u otra según la validación no correcta que se haya producido en el input.
Por ultimo podemos ver el código del ts componente padre en el que accedemos a la infomación del fomulario al hacer submit:
export class AppComponent {
onSubmit(form :NgForm){
console.log(form)
console.log(form.form.value.userName)
}
}
Como podemos ver la funcion onSubmit
recibe el ngForm como parametro, y de ahi podemos extraer la información del formularioTemplate-driven forms | Forms | Angular